package edu.pitt.dbmi.odie.gapp.gwt.server.document;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.TreeSet;

import javax.jdo.PersistenceManager;

import com.allen_sauer.gwt.log.client.Log;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gdata.client.docs.DocsService;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.docs.DocumentListFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import edu.pitt.dbmi.odie.gapp.gwt.client.document.ODIE_ObaService;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Analysis;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Annotation;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Cls;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_Document;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_PMF;
import edu.pitt.dbmi.odie.gapp.gwt.model.ODIE_User;
import edu.pitt.dbmi.odie.gapp.gwt.server.navigator.ODIE_SelectionCacher;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_DocsServiceFetcher;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_DocumentEntitySAXParser;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_GDataDocsService;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.gdata.ODIE_GDataDocument;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.marginizer.ODIE_Marginizer;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.oba.ODIE_ObaAnnotationBean;
import edu.pitt.dbmi.odie.gapp.gwt.server.util.oba.ODIE_ObaCoder;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class ODIE_ObaServiceImpl extends RemoteServiceServlet implements
		ODIE_ObaService {
	
	private static final ODIE_Marginizer marginizer = new ODIE_Marginizer();
	private static ODIE_ObaCoder coder = null ;

	private ODIE_GDataDocsService docsService = null;
	private ODIE_SelectionCacher selectionCacher = null ;
	
	private static final Comparator<ODIE_ObaAnnotationBean> overlapComparator = new Comparator<ODIE_ObaAnnotationBean>() {
		public int compare(ODIE_ObaAnnotationBean o1,
				ODIE_ObaAnnotationBean o2) {
			int ret = (int) (o1.getSPos() - o2.getSPos());
			if ((o1.getSPos() <= o2.getSPos() && o1
					.getEPos() >= o2.getSPos())
					|| (o1.getEPos() >= o2.getSPos() && o1
							.getEPos() <= o2.getEPos())) {
				ret = 0;
			}
			return ret;
		}
	};

	public String autoCode(String directoryHandle, String title) {
		Log.debug("Calling autocode with directoryHandle ==>"
				+ directoryHandle);
		try {
			this.docsService = ODIE_DocsServiceFetcher
					.fetchDocsService(this);
			this.selectionCacher = ODIE_SelectionCacher.fetchOdieSelectionCacher(this) ;
			if (this.docsService != null || this.selectionCacher == null) {
				if (coder == null) {
					coder = new ODIE_ObaCoder() ;
					coder.initialize(this.selectionCacher.getLoginInfo()) ;
				}
				processDocumentsInFolder(this.docsService, directoryHandle, title);
			}
		} catch (AuthenticationException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ServiceException e) {
			e.printStackTrace();
		}
		
		return null;
	}

	private void processDocumentsInFolder(ODIE_GDataDocsService docsService,
			String folderId, String title) throws IOException,
			MalformedURLException, ServiceException {
		folderId = extractIdFromUrl(folderId);
		// Log.debug("Got folder named " + folderId);
		String urlName = "http://" + ODIE_GDataDocsService.DEFAULT_HOST + ODIE_GDataDocsService.URL_FEED;
		urlName += ODIE_GDataDocsService.URL_GROUP_FOLDERS + ODIE_GDataDocsService.URL_PATH + "/" + folderId;
		// Log.debug(urlName);
		URL url = new URL(urlName);
		DocumentListFeed feed = docsService.getDocsService()
				.getFeed(url, DocumentListFeed.class);
		ArrayList<ODIE_GDataDocument> documentObjs = new ArrayList<ODIE_GDataDocument>();
		for (DocumentListEntry entry : feed.getEntries()) {
			String documentXml = docsService
					.extractDocumentXml(entry);
			// Log.debug(prettyXmlFragment(documentXml));
			ODIE_DocumentEntitySAXParser.getInstance().parseDocument(
					documentXml);
			documentObjs.add(ODIE_DocumentEntitySAXParser.getInstance()
					.getOdieGDataDocument());
		}
		// Log.debug("\nThere are " + documentIds.size() +
		// " documents ");
		for (ODIE_GDataDocument document : documentObjs) {
			String documentId = document.getRawId();
			String documentText = docsService.pullDocumentContent(
					docsService.stripLeadingDocumentType(documentId));		
			marginizer.setInputFileAsString(documentText);
			marginizer.execute();
			documentText = marginizer.getOutputFileAsString();
			coder.setDocumentText(documentText);
			coder.execute();
			ArrayList<ODIE_ObaAnnotationBean> documentAnnotations = new ArrayList<ODIE_ObaAnnotationBean>();
			documentAnnotations.addAll(coder.getAnnotations());
			document.setAnnotations(documentAnnotations);

			for (ODIE_ObaAnnotationBean annot : document.getAnnotations()) {
				Log.debug(annot.toString());
			}
		}
		// Log.debug("Finished displaying documentIds");
		createNewAnalysis(title, documentObjs);
	}

	private void createNewAnalysis(String title,
			ArrayList<ODIE_GDataDocument> documentObjs) {
		PersistenceManager pm = ODIE_PMF.get().getPersistenceManager();
		ArrayList<Object> objsToPersist = new ArrayList<Object>();
		Stack<Object> objsStack = new Stack<Object>();
		UserService userService = UserServiceFactory.getUserService();
		User user = userService.getCurrentUser();
		Date creationDate = new Date();
		title = timeStampTitle(title, creationDate) ;
		ODIE_Analysis odieAnalysis = new ODIE_Analysis() ;
		ODIE_User odieUser = this.selectionCacher.fetchOrCreateUser(user.getEmail(), user.getNickname()) ;
		odieAnalysis.setUserId(odieUser.getOdieId()) ;
		odieAnalysis.setTitle(title) ;
		odieAnalysis.setCreationDate(creationDate) ;
		odieAnalysis.setModifiedDate(creationDate) ;
		objsToPersist.add(odieAnalysis);
		objsStack.push(odieAnalysis);
		for (Iterator<ODIE_GDataDocument> iterator = documentObjs.iterator(); iterator
				.hasNext();) {
			ODIE_GDataDocument odieGDataDocument = iterator.next();
			ODIE_Document odieDocument = new ODIE_Document();
			odieDocument.setOdieAnalysisId(odieAnalysis.getOdieId());
			odieDocument.setTitle(odieGDataDocument.getTitle());
			odieDocument.setAtomId(odieGDataDocument.getAtomId());
			odieDocument.setRawId(odieGDataDocument.getRawId());
			objsToPersist.add(odieDocument);
			objsStack.push(odieDocument);
			if (odieGDataDocument.getAnnotations().size()>0) {
				TreeSet<ODIE_ObaAnnotationBean> nonOverlappingAnnotations = new TreeSet<ODIE_ObaAnnotationBean>(overlapComparator) ;
				nonOverlappingAnnotations.addAll(odieGDataDocument
					.getAnnotations()) ;
				for (Iterator<ODIE_ObaAnnotationBean> annotationIterator = nonOverlappingAnnotations.iterator(); annotationIterator.hasNext(); ) {
					ODIE_ObaAnnotationBean annotation = annotationIterator.next();
					ODIE_Annotation odieAnnotation = new ODIE_Annotation();
					odieAnnotation.setOdieAnalysisId(odieAnalysis.getOdieId());
					odieAnnotation.setOdieDocumentId(odieDocument.getOdieId());
					odieAnnotation.setSPos(annotation.getSPos());
					odieAnnotation.setEPos(annotation.getEPos());
					ODIE_Cls odieCls = fetchOrCreateCls(pm, annotation, odieAnalysis);
					odieAnnotation.setOntologyName(odieCls.getOntologyName());
				    Log.debug("Setting odieAnnotation.odieClsId to " + odieCls.getOdieId()) ;
					odieAnnotation.setOdieClsId(odieCls.getOdieId()) ;
					odieAnnotation.setClsName(odieCls.getClsName());
					objsToPersist.add(odieAnnotation);
					objsStack.push(odieAnnotation);
				}
			}
		}
		try {
			while (!objsStack.isEmpty()) {
				pm.makePersistent(objsStack.pop());
			}
			pm.close();
		} catch (Exception x) {
			Log.fatal(x.getMessage());
		}

		ODIE_SelectionCacher selectionCacher = ODIE_SelectionCacher
				.fetchOdieSelectionCacher(this);
		if (selectionCacher != null) {
			selectionCacher.selectAnalysisById(odieAnalysis.getOdieId());
		}

	}

	private String timeStampTitle(String title, Date creationDate) {
		String formatTemplate = "_yyyy_MM_dd_HH_mm_ss_SSS" ;
		SimpleDateFormat df =
            new SimpleDateFormat(formatTemplate);
		return title + df.format(creationDate) ;
	}

	@SuppressWarnings("unchecked")
	private ODIE_Cls fetchOrCreateCls(PersistenceManager pm,
			ODIE_ObaAnnotationBean odieAnnotation, ODIE_Analysis odieAnalysis) {
		ODIE_Cls result = null;
		String query = "select from " + ODIE_Cls.class.getName();
		List<ODIE_Cls> clses = (List<ODIE_Cls>) pm.newQuery(query).execute();
		if (!clses.isEmpty()) {
			for (ODIE_Cls odieCls : clses) {
				if (odieCls.getCui().equals(odieAnnotation.getCui()) 
						&& odieCls.getOdieAnalysisId().equals(odieAnalysis.getOdieId())) {
					result = odieCls;
					break;
				}
			}
		}
		if (result == null) {
			result = new ODIE_Cls();
			String ontologyName = odieAnnotation.getLocalOntologyID();
			String clsName = odieAnnotation.getPreferredName();
			String cui = odieAnnotation.getCui();
			Log.debug("Caching new Cls (" + ontologyName + ", "
					+ clsName + ", " + cui + ")");
			result.setOdieAnalysisId(odieAnalysis.getOdieId()) ;
			result.setOntologyName(ontologyName);
			result.setClsName(clsName);
			result.setColorAsString("magenta");
			result.setCui(cui);
			try {
				pm.makePersistent(result);
			} catch (Exception x) {
				x.printStackTrace();
			}
		}
		return result;
	}

	private String extractIdFromUrl(String urlName) {
		int sPos = urlName.lastIndexOf("/");
		return urlName.substring(sPos + 1, urlName.length());
	}

	public DocumentListEntry pullEntryByFullId(DocsService docsService,
			String fullId) {
		DocumentListEntry entry = null;
		URL documentURI;
		try {
			documentURI = new URL(
					"http://docs.google.com/feeds/documents/private/full/"
							+ fullId);
			entry = docsService.getEntry(documentURI, DocumentListEntry.class);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ServiceException e) {
			e.printStackTrace();
		}
		return entry;
	}

}
